package com.jie.client.core.handler;

import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.symmetric.AES;
import com.jie.client.core.properties.ClientProperties;
import com.jie.client.core.runnable.ClientReadHandleRunnable;
import com.jie.client.core.runnable.ViewRunnable;
import com.jie.client.core.ssl.entity.ClientSslMessage;
import com.jie.client.core.ssl.properties.CaProperties;
import com.jie.common.ssl.constant.SslConstant;
import com.jie.common.ssl.util.AesUtil;
import com.jie.common.ssl.util.DigestUtil;
import com.jie.common.util.BytesUtil;
import com.jie.common.util.InputStreamUtil;
import com.jie.common.util.OutputStreamUtil;
import com.jie.common.util.RandomUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author XieZhiJie
 * @date 2022/04/22 14:51
 */
public class ClientHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(ClientHandler.class);

    private static final int CORE_POOL_SIZE = 2;

    private static final int MAX_POOL_SIZE = 3;

    private static final int QUEUE_CAPACITY = 1;

    private static final Long KEEP_ALIVE_TIME = 1L;

    public static final ClientSslMessage CLIENT_SSL_MESSAGE = new ClientSslMessage();

    public static void run() {
        // 弄一个线程池, 核心线程数只有一个, 用于处理读事件
        // 写事件由主线程完成
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(QUEUE_CAPACITY),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
        try (
                Socket socket = new Socket(ClientProperties.SERVER_IP, ClientProperties.SERVER_PORT)
        ) {

            // ssl4次握手
            sslHandShake(SslConstant.OPEN_SSL, socket.getInputStream(), socket.getOutputStream());

            LOGGER.info("客户端成功连接服务端!");

            // 写
            executor.execute(new ClientReadHandleRunnable(socket));

            // 启动图形化界面
            executor.execute(new ViewRunnable());

            // 读
            ClientWriteHandler.handleWrite(socket);
        } catch (IOException e) {
            LOGGER.warn(e.getMessage());
        } catch (Exception e) {
            LOGGER.error(e.getMessage());
        }
        executor.shutdown();
        LOGGER.info("客户端结束!");
    }

    /**
     * flag为真则开启四次握手
     * @param flag         flag
     * @param inputStream  inputStream
     * @param outputStream outputStream
     * @throws Exception   Exception
     */
    private static void sslHandShake(boolean flag, InputStream inputStream, OutputStream outputStream) throws Exception {
        if (flag) {
            sslHandShake(inputStream, outputStream);
        }
    }

    /**
     * 客户端用于和服务端的ssl握手
     * @param inputStream  inputStream
     * @param outputStream outputStream
     * @throws Exception   Exception
     */
    private static void sslHandShake(InputStream inputStream, OutputStream outputStream) throws Exception {
        // 1.生成一个随机数x并发送
        byte[] firstHandShakeBytes = RandomUtil.getRandomBytes();
        OutputStreamUtil.writeMessage(outputStream, firstHandShakeBytes);

        // 2.接收到第二次握手内容(随机数y+服务器证书)
        byte[] secondHandShakeBytes = InputStreamUtil.readMessage(inputStream);
        byte[][] splitPack = BytesUtil.splitBytesToTwoBytes(secondHandShakeBytes);
        byte[] randomServerBytes = splitPack[0];

        // 3.1.验证服务器证书, 成功则生成随机数pre
        byte[] serverPublicKeyBytes = verifyCertificate(splitPack[1]);
        if (serverPublicKeyBytes == null) {
            // 验证失败
            throw new Exception("验证证书失败!");
        }
        byte[] randomPreBytes = RandomUtil.getRandomBytes();

        // 3.2.xyz一起生成一个会话密钥
        CLIENT_SSL_MESSAGE.setAes(new AES(AesUtil.generateSessionKey(firstHandShakeBytes, randomServerBytes, randomPreBytes)));

        // 3.3.将前两次握手内容SHA1一下得到一个摘要
        byte[] digestBytes = BytesUtil.packetBytes(firstHandShakeBytes, secondHandShakeBytes);
        digestBytes = DigestUtil.generateDigest(digestBytes);

        // 3.4.第三次握手用服务器公钥加密(pre+摘要)并发送
        byte[] thirdHandShakeBytes = BytesUtil.packageTwoBytesWithLengthBytes(randomPreBytes, digestBytes);
        thirdHandShakeBytes = new RSA(null, serverPublicKeyBytes).encrypt(thirdHandShakeBytes, KeyType.PublicKey);
        OutputStreamUtil.writeMessage(outputStream, thirdHandShakeBytes);

        // 4.接收第四次握手内容, 用会话密钥解密, 并验证(第四次握手是前三次握手内容的摘要)
        byte[] fourthHandShakeBytes = InputStreamUtil.readMessage(inputStream);
        fourthHandShakeBytes = AesUtil.decrypt(fourthHandShakeBytes, ClientHandler.CLIENT_SSL_MESSAGE.getAes());
        if (!DigestUtil.verifyDigest(fourthHandShakeBytes, firstHandShakeBytes, secondHandShakeBytes, thirdHandShakeBytes)) {
            throw new Exception("第四次握手摘要验证失败!");
        }
    }

    /**
     * 验证证书并返回服务器公钥
     * @param certificateBytes 服务器证书字节数组
     * @return                 验证成功返回服务器公钥字节数组, 否则返回null
     */
    public static byte[] verifyCertificate(byte[] certificateBytes) {
        // 用CA公钥来验证
        RSA rsa = new RSA(null, CaProperties.PUBLIC_KEY_BYTES);
        byte[] decrypt = rsa.decrypt(certificateBytes, KeyType.PublicKey);
        byte[][] array = BytesUtil.splitBytesToTwoBytes(decrypt);
        byte[] serverPublicKeyBytes = array[0];
        byte[] digest = array[1];
        if (SecureUtil.sha1(new String(serverPublicKeyBytes)).equals(new String(digest))) {
            return serverPublicKeyBytes;
        }
        return null;
    }

}
